package com.example.videoplayer.service.impl;

import com.example.videoplayer.model.Category;
import com.example.videoplayer.model.Video;
import com.example.videoplayer.repository.CategoryRepository;
import com.example.videoplayer.repository.VideoRepository;
import com.example.videoplayer.service.HLSConversionService;
import com.example.videoplayer.service.VideoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
public class VideoServiceImpl implements VideoService {
    
    private static final Logger logger = LoggerFactory.getLogger(VideoServiceImpl.class);
    private final ResourceLoader resourceLoader;
    private final HLSConversionService hlsConversionService;
    private final VideoRepository videoRepository;
    private final CategoryRepository categoryRepository;
    private static final int CHUNK_SIZE = 1024 * 1024; // 1MB chunks
    private static final String VIDEOS_DIR = "src/main/resources/videos/";

    public VideoServiceImpl(ResourceLoader resourceLoader, 
                          HLSConversionService hlsConversionService,
                          VideoRepository videoRepository,
                          CategoryRepository categoryRepository) {
        this.resourceLoader = resourceLoader;
        this.hlsConversionService = hlsConversionService;
        this.videoRepository = videoRepository;
        this.categoryRepository = categoryRepository;
        createVideosDirectory();
    }

    private void createVideosDirectory() {
        File directory = new File(VIDEOS_DIR);
        if (!directory.exists()) {
            directory.mkdirs();
        }
    }

    @Override
    public Mono<ResponseEntity<Resource>> streamVideo(String fileName, String range) {
        return Mono.fromCallable(() -> {
            Resource video = resourceLoader.getResource("classpath:videos/" + fileName);
            return prepareVideoResponse(video, range);
        }).subscribeOn(Schedulers.boundedElastic());
    }

    @Override
    public Mono<Video> uploadVideo(FilePart file, String title, String category, String description) {
        String fileName = generateUniqueFileName(file.filename());
        Path path = Paths.get(VIDEOS_DIR + fileName);
        
        Video video = new Video();
        video.setTitle(title);
        video.setFileName(fileName);
        video.setCategory(category);
        video.setDescription(description);
        video.setUploadTime(LocalDateTime.now());
        video.setViews(0L);
        video.setThumbnailUrl("/images/thumbnails/default.jpg");
        video.setStatus("PROCESSING");

        return categoryRepository.findByName(category)
            .switchIfEmpty(
                categoryRepository.save(new Category(category, category + "类视频"))
            )
            .then(file.transferTo(path))
            .then(Mono.fromCallable(() -> {
                generateThumbnail(path.toString(), fileName, video);
                return video;
            }))
            .then(videoRepository.save(video))
            .flatMap(savedVideo -> 
                hlsConversionService.convertToHLS(path.toString())
                    .doOnSuccess(v -> {
                        savedVideo.setStatus("READY");
                        videoRepository.save(savedVideo).subscribe();
                    })
                    .doOnError(e -> {
                        logger.error("Error converting video: {}", e.getMessage());
                        savedVideo.setStatus("ERROR");
                        videoRepository.save(savedVideo).subscribe();
                    })
                    .thenReturn(savedVideo)
            )
            .subscribeOn(Schedulers.boundedElastic());
    }

    @Override
    public Mono<String> getHLSStreamUrl(String fileName) {
        String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
        return Mono.just("/hls/" + baseName + "/playlist.m3u8");
    }

    @Override
    public Flux<Video> getAllVideos(int page, int size) {
        return videoRepository.findAllPaginated(size, page * size);
    }

    @Override
    public Flux<Video> getVideosByCategory(String category, int page, int size) {
        return videoRepository.findByCategoryPaginated(category, size, page * size);
    }

    @Override
    public Mono<Video> getVideoById(Long id) {
        return videoRepository.findById(id);
    }

    @Override
    public Mono<List<String>> getAllCategories() {
        return categoryRepository.findAllByOrderByNameAsc()
                .map(Category::getName)
                .collectList();
    }

    private String generateUniqueFileName(String originalFilename) {
        String extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
        return System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + extension;
    }

    private ResponseEntity<Resource> prepareVideoResponse(Resource video, String range) throws IOException {
        long contentLength = video.contentLength();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("video/mp4"));

        if (range == null) {
            return prepareFullVideoResponse(video, headers, contentLength);
        }
        return preparePartialVideoResponse(video, headers, contentLength, range);
    }

    private ResponseEntity<Resource> prepareFullVideoResponse(Resource video, HttpHeaders headers, long contentLength) {
        headers.set("Accept-Ranges", "bytes");
        headers.setContentLength(contentLength);
        return ResponseEntity.status(HttpStatus.OK)
                .headers(headers)
                .body(video);
    }

    private ResponseEntity<Resource> preparePartialVideoResponse(
            Resource video, HttpHeaders headers, long contentLength, String range) {
        
        String[] ranges = range.replace("bytes=", "").split("-");
        long start = Long.parseLong(ranges[0]);
        long end = ranges.length > 1 ? 
                Long.parseLong(ranges[1]) : 
                Math.min(start + CHUNK_SIZE, contentLength - 1);

        headers.add("Content-Range", String.format("bytes %d-%d/%d", start, end, contentLength));
        headers.setContentLength(end - start + 1);

        return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
                .headers(headers)
                .body(video);
    }

    private void generateThumbnail(String videoPath, String fileName, Video video) {
        try {
            String thumbnailDir = "src/main/resources/thumbnails/";
            Files.createDirectories(Paths.get(thumbnailDir));
            String thumbnailPath = thumbnailDir + fileName.substring(0, fileName.lastIndexOf('.')) + ".jpg";
            
            List<String> command = new ArrayList<>();
            command.add("ffmpeg");
            command.add("-i");
            command.add(videoPath);
            command.add("-ss");
            command.add("00:00:02");  // 从视频第2秒截图
            command.add("-vframes");
            command.add("1");
            command.add("-vf");
            command.add("scale=480:-1");  // 设置缩略图宽度为480px，高度自适应
            command.add("-q:v");
            command.add("2");  // 高质量
            command.add(thumbnailPath);

            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true);
            Process process = pb.start();
            process.waitFor();

            // 更新视频的缩略图URL
            String thumbnailUrl = "/thumbnails/" + fileName.substring(0, fileName.lastIndexOf('.')) + ".jpg";
            video.setThumbnailUrl(thumbnailUrl);
            
            logger.info("Generated thumbnail for video: {}, thumbnail path: {}", fileName, thumbnailUrl);
        } catch (Exception e) {
            logger.error("Error generating thumbnail for {}: {}", fileName, e.getMessage());
        }
    }
} 